﻿using System;
using System.Collections.Generic;
using System.Numerics;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 信号处理辅助类
    /// </summary>
    public class SignalHelper
    {

        #region FFTShift

        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="complexs">复数数组</param>
        /// <returns>迁移后的复数数组</returns>
        /// <exception cref="ArgumentNullException">复数数组为null将抛出异常</exception>
        public static Complex[] FFTShift(Complex[] complexs)
        {
            if (complexs == null)
            {
                throw new ArgumentNullException(nameof(complexs));
            }

            if (complexs.Length == 0)
            {
                return complexs;
            }

            return FFTShift(complexs, 0, complexs.Length);
        }

        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="complexs">复数数据片</param>
        /// <returns>迁移后的实数数组</returns>
        /// <exception cref="ArgumentNullException">实数数组为null将抛出异常</exception>
        public static Complex[] FFTShift(ValueTypeFlexibleArray<Complex> complexs)
        {
            //if (complexs == null)
            //{
            //    throw new ArgumentNullException(nameof(complexs));
            //}

            //return FFTShift(complexs.Array, complexs.Offset, complexs.Length);
            //todo FFTShift
            throw new NotImplementedException();
        }

        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="complexs">复数数组</param>
        /// <param name="startIndex">数组中起始位置,小于0表示从起始位置开始</param>
        /// <param name="length">迁移数据长度,小于0表示到结束</param>
        /// <returns>迁移后的实数数组</returns>
        /// <exception cref="ArgumentNullException">实数数组为null将抛出异常</exception>
        public static Complex[] FFTShift(Complex[] complexs, int startIndex, int length)
        {
            if (complexs == null)
            {
                throw new ArgumentNullException(nameof(complexs));
            }

            if (startIndex < 0)
            {
                startIndex = 0;
            }

            if (length < 0)
            {
                length = complexs.Length - startIndex;
            }
            else if (length > 0)
            {
                if (startIndex + length > complexs.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length), $"迁移数据长度\"{length}\"超出范围,最大只能为\"{complexs.Length - startIndex}\"");
                }
            }

            if (length == 0)
            {
                return new Complex[0];
            }

            Complex[] result = new Complex[length];
            int loc = length / 2;
            if (length % 2 == 0)
            {
                //长度为偶数
                //src 1,2,3,4 => 3,4,1,2
                Array.Copy(complexs, startIndex + loc, result, 0, loc);//源数组后半数据
                Array.Copy(complexs, startIndex, result, loc, loc);//源数组前半数据
            }
            else
            {
                //长度为奇数
                //src 1,2,3,4,5 => 4,5,1,2,3
                Array.Copy(complexs, startIndex + loc + 1, result, 0, loc);//源数组后半数据
                Array.Copy(complexs, startIndex, result, loc, loc + 1);//源数组前半数据
            }
            return result;
        }





        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="reals">实数数组</param>
        /// <returns>迁移后的实数数组</returns>
        /// <exception cref="ArgumentNullException">实数数组为null将抛出异常</exception>
        public static double[] FFTShift(double[] reals)
        {
            if (reals == null)
            {
                throw new ArgumentNullException(nameof(reals));
            }

            if (reals.Length == 0)
            {
                return reals;
            }

            return FFTShift(reals, 0, reals.Length);
        }

        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="reals">实数数据片</param>
        /// <returns>迁移后的实数数组</returns>
        /// <exception cref="ArgumentNullException">实数数组为null将抛出异常</exception>
        public static double[] FFTShift(ValueTypeFlexibleArray<double> reals)
        {
            //if (reals == null)
            //{
            //    throw new ArgumentNullException(nameof(reals));
            //}

            //return FFTShift(reals.Array, reals.Offset, reals.Length);
            //todo FFTShift
            throw new NotImplementedException();
        }

        /// <summary>
        /// 对傅里叶变换后数据迁移
        /// </summary>
        /// <param name="reals">实数数组</param>
        /// <param name="startIndex">数组中起始位置,小于0表示从起始位置开始</param>
        /// <param name="length">迁移数据长度,小于0表示到结束</param>
        /// <returns>迁移后的实数数组</returns>
        /// <exception cref="ArgumentNullException">实数数组为null将抛出异常</exception>
        public static double[] FFTShift(double[] reals, int startIndex, int length)
        {
            if (reals == null)
            {
                throw new ArgumentNullException(nameof(reals));
            }

            if (startIndex < 0)
            {
                startIndex = 0;
            }

            if (length < 0)
            {
                length = reals.Length - startIndex;
            }
            else if (length > 0)
            {
                if (startIndex + length > reals.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length), $"迁移数据长度\"{length}\"超出范围,最大只能为\"{reals.Length - startIndex}\"");
                }
            }

            if (length == 0)
            {
                return new double[0];
            }

            double[] result = new double[length];
            var mul = sizeof(double);
            int loc = length / 2;
            //注Buffer.BlockCopy操作的是字节,所以拷贝数据为非byte类型时,必须乘以数据类型所占字节数

            if (length % 2 == 0)
            {
                //长度为偶数
                //src 1,2,3,4 => 3,4,1,2
                Buffer.BlockCopy(reals, (startIndex + loc) * mul, result, 0 * mul, loc * mul);//源数组后半数据
                Buffer.BlockCopy(reals, startIndex * mul, result, loc * mul, loc * mul);//源数组前半数据
            }
            else
            {
                //长度为奇数
                //src 1,2,3,4,5 => 4,5,1,2,3
                Buffer.BlockCopy(reals, (startIndex + loc + 1) * mul, result, 0 * mul, loc * mul);//源数组后半数据
                Buffer.BlockCopy(reals, startIndex * mul, result, loc * mul, (loc + 1) * mul);//源数组前半数据
            }

            return result;
        }

        #endregion

        #region CaculatePower

        /// <summary>
        /// 计算复数能量值,同时内部还做了蝶变数据块搬迁和数据反转
        /// </summary>
        /// <param name="complexs">复数数组</param>
        /// <param name="noise">噪音</param>
        /// <param name="sample">采样率</param>
        /// <param name="logMul">log倍数,默认10</param>
        /// <returns>复数能量值</returns>
        public static double[] CaculatePower(Complex[] complexs, double noise, int sample, int logMul = 10)
        {
            if (complexs == null)
            {
                throw new ArgumentNullException(nameof(complexs));
            }

            //int midIndex = complexs.Length / 2;
            //double timeInterval = (double)complexs.Length / sample;
            //double[] results = new double[complexs.Length];
            //int index = complexs.Length - 1;
            //double value;

            //for (int i = midIndex; i < complexs.Length; i++)
            //{
            //    value = Abs(complexs[i]);
            //    value = Math.Pow(value / midIndex, 2) / timeInterval;
            //    value = logMul * Math.Log10(value);
            //    value += noise;
            //    results[index] = value;
            //    index--;
            //}

            //for (int i = 0; i < midIndex; i++)
            //{
            //    value = Abs(complexs[i]);
            //    value = Math.Pow(value / midIndex, 2) / timeInterval;
            //    value = logMul * Math.Log10(value);
            //    value += noise;
            //    results[index] = value;
            //    index--;
            //}

            //return results;

            return CaculatePower(complexs, 0, complexs.Length, noise, sample, logMul);
        }

        /// <summary>
        /// 计算复数能量值,同时内部还做了蝶变数据块搬迁和数据反转
        /// </summary>
        /// <param name="complexs">复数数组</param>
        /// <param name="startIndex">数组中起始位置,小于0表示从起始位置开始</param>
        /// <param name="length">迁移数据长度,小于0表示到结束</param>
        /// <param name="noise">噪音</param>
        /// <param name="sample">采样率</param>
        /// <param name="logMul">log倍数,默认10</param>
        /// <returns>复数能量值</returns>
        public static double[] CaculatePower(Complex[] complexs, int startIndex, int length, double noise, int sample, int logMul = 10)
        {
            if (complexs == null)
            {
                throw new ArgumentNullException(nameof(complexs));
            }

            if (startIndex < 0)
            {
                startIndex = 0;
            }

            if (length < 0)
            {
                length = complexs.Length - startIndex;
            }
            else if (length > 0)
            {
                if (startIndex + length > complexs.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length), $"目标数据长度\"{length}\"超出范围,最大只能为\"{complexs.Length - startIndex}\"");
                }
            }

            if (length == 0)
            {
                return new double[0];
            }

            if (sample <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(sample), "采样率不能小于等于0");
            }

            double[] results = new double[length];
            int loc = length / 2;
            double timeInterval = (double)length / sample;
            int resultIndex = length - 1, midIndex;
            double value, resultValue;
            if (length % 2 == 0)
            {
                //长度为偶数(src 1,2,3,4 => 3,4,1,2)
                midIndex = startIndex + loc;
            }
            else
            {
                //长度为奇数(src 1,2,3,4,5 => 4,5,1,2,3)
                midIndex = startIndex + loc + 1;
            }

            List<int> invalidIndexList = null;
            int endIndex = startIndex + length;
            for (int i = midIndex; i < endIndex && resultIndex >= 0; i++)
            {
                //results[resultIndex] = complexs[i].Real;
                value = Complex.Abs(complexs[i]);
                if (TryCaculatePower(value, ref loc, ref timeInterval, ref logMul, ref noise, out resultValue))
                {
                    results[resultIndex] = resultValue;
                }
                else
                {
                    if (invalidIndexList == null)
                    {
                        invalidIndexList = new List<int>();
                    }
                    invalidIndexList.Add(resultIndex);
                }

                resultIndex--;
            }

            for (int i = startIndex; i < midIndex && resultIndex >= 0; i++)
            {
                //results[resultIndex] = complexs[i].Real;
                value = Complex.Abs(complexs[i]);
                if (TryCaculatePower(value, ref loc, ref timeInterval, ref logMul, ref noise, out resultValue))
                {
                    results[resultIndex] = resultValue;
                }
                else
                {
                    if (invalidIndexList == null)
                    {
                        invalidIndexList = new List<int>();
                    }
                    invalidIndexList.Add(resultIndex);
                }
                resultIndex--;
            }

            //处理异常值
            if (invalidIndexList != null)
            {
                ProcessPowerAbnormalValue(invalidIndexList, results);
            }

            return results;
        }


        /// <summary>
        /// 计算复数能量值,同时内部还做了蝶变数据块搬迁和数据反转
        /// </summary>
        /// <param name="real">实数数组</param>
        /// <param name="noise">噪音</param>
        /// <param name="sample">采样率</param>
        /// <param name="logMul">log倍数,默认10</param>
        /// <returns>复数能量值</returns>
        public static double[] CaculatePower(double[] real, double noise, int sample, int logMul = 10)
        {
            if (real == null)
            {
                throw new ArgumentNullException(nameof(real));
            }

            if (real.Length == 0)
            {
                return new double[0];
            }

            //int midIndex = real.Length / 2;
            //double timeInterval = (double)real.Length / sample;
            //double[] results = new double[real.Length];
            //int index = real.Length - 1;
            //double value;

            //for (int i = midIndex; i < real.Length; i++)
            //{
            //    value = Math.Abs(real[i]);
            //    value = Math.Pow(value / midIndex, 2) / timeInterval;
            //    value = logMul * Math.Log10(value);
            //    value += noise;
            //    results[index] = value;
            //    index--;
            //}

            //for (int i = 0; i < midIndex; i++)
            //{
            //    value = Math.Abs(real[i]);
            //    value = Math.Pow(value / midIndex, 2) / timeInterval;
            //    value = logMul * Math.Log10(value);
            //    value += noise;
            //    results[index] = value;
            //    index--;
            //}

            //return results;

            return CaculatePower(real, 0, real.Length, noise, sample, logMul);
        }

        /// <summary>
        /// 计算复数能量值,同时内部还做了蝶变数据块搬迁和数据反转
        /// </summary>
        /// <param name="real">实数数组</param>
        /// <param name="startIndex">数组中起始位置,小于0表示从起始位置开始</param>
        /// <param name="length">迁移数据长度,小于0表示到结束</param>
        /// <param name="noise">噪音</param>
        /// <param name="sample">采样率</param>
        /// <param name="logMul">log倍数,默认10</param>
        /// <returns>复数能量值</returns>
        public static double[] CaculatePower(double[] real, int startIndex, int length, double noise, int sample, int logMul = 10)
        {
            if (real == null)
            {
                throw new ArgumentNullException(nameof(real));
            }

            if (startIndex < 0)
            {
                startIndex = 0;
            }

            if (length < 0)
            {
                length = real.Length - startIndex;
            }
            else if (length > 0)
            {
                if (startIndex + length > real.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length), $"目标数据长度\"{length}\"超出范围,最大只能为\"{real.Length - startIndex}\"");
                }
            }

            if (length == 0)
            {
                return new double[0];
            }

            if (sample <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(sample), "采样率不能小于等于0");
            }

            double[] results = new double[length];
            int loc = length / 2;
            double timeInterval = (double)length / sample;
            int resultIndex = length - 1, midIndex;
            double value;
            if (length % 2 == 0)
            {
                //长度为偶数(src 1,2,3,4 => 3,4,1,2)
                midIndex = startIndex + loc;
            }
            else
            {
                //长度为奇数(src 1,2,3,4,5 => 4,5,1,2,3)
                midIndex = startIndex + loc + 1;
            }

            double resultValue;
            List<int> invalidIndexList = null;
            int endIndex = startIndex + length;
            for (int i = midIndex; i < endIndex && resultIndex >= 0; i++)
            {
                //results[resultIndex] = real[i];
                if (DoubleEx.Valid(real[i]))
                {
                    value = Math.Abs(real[i]);
                    if (TryCaculatePower(value, ref loc, ref timeInterval, ref logMul, ref noise, out resultValue))
                    {
                        results[resultIndex] = resultValue;
                        resultIndex--;
                        continue;
                    }
                }

                if (invalidIndexList == null)
                {
                    invalidIndexList = new List<int>();
                }
                invalidIndexList.Add(resultIndex);
                resultIndex--;
            }

            for (int i = startIndex; i < midIndex && resultIndex >= 0; i++)
            {
                //results[resultIndex] = real[i];
                if (DoubleEx.Valid(real[i]))
                {
                    value = Math.Abs(real[i]);
                    if (TryCaculatePower(value, ref loc, ref timeInterval, ref logMul, ref noise, out resultValue))
                    {
                        results[resultIndex] = resultValue;
                        resultIndex--;
                        continue;
                    }
                }

                if (invalidIndexList == null)
                {
                    invalidIndexList = new List<int>();
                }
                invalidIndexList.Add(resultIndex);
                resultIndex--;
            }

            //处理异常值
            if (invalidIndexList != null)
            {
                ProcessPowerAbnormalValue(invalidIndexList, results);
            }

            return results;
        }



        private static bool TryCaculatePower(double value, ref int loc, ref double timeInterval, ref int logMul, ref double noise, out double result)
        {
            value = Math.Pow(value / loc, 2) / timeInterval;
            value = Math.Log10(value);
            if (DoubleEx.Valid(value))
            {
                result = logMul * value + noise;
                return true;
            }
            else
            {
                result = double.NaN;
                return false;
            }
        }

        private static void ProcessPowerAbnormalValue(List<int> invalidIndexList, double[] results)
        {
            //ZLog.ZLoger.Warn($"索引:{string.Join(',', invalidIndexList)}处数据异常,尝试修正");
            ZLog.ZLoger.Warn($"索引:{string.Join(",", invalidIndexList)}处数据异常,对应索引的值设置为double.NaN");

            foreach (var index in invalidIndexList)
            {
                results[index] = double.NaN;
            }
        }

        #endregion


       

        #region 图表显示数据压缩

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="srcSampleRate"></param>
        /// <param name="dstSampleRate"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static T[] Resample<T>(ValueTypeFlexibleArray<T> data, int srcSampleRate, int dstSampleRate, IResampleFilter<T> filter) where T : struct, IComparable
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            if (data.Length == 0)
            {
                return new T[0];
            }

            //return Resample(data.Array, data.Offset, data.Length, srcSampleRate, dstSampleRate, filter);
            //todo FFTShift
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="data"></param>
        /// <param name="srcSampleRate"></param>
        /// <param name="dstSampleRate"></param>
        /// <param name="filter"></param>
        /// <returns></returns>
        public static T[] Resample<T>(T[] data, int srcSampleRate, int dstSampleRate, IResampleFilter<T> filter) where T : struct, IComparable
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            if (data.Length == 0)
            {
                return new T[0];
            }

            return Resample(data, 0, data.Length, srcSampleRate, dstSampleRate, filter);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="data">原始数据</param>
        /// <param name="startIndex">数组中起始位置,小于0表示从起始位置开始</param>
        /// <param name="length">迁移数据长度,小于0表示到结束</param>
        /// <param name="srcSampleRate">原始采样率</param>
        /// <param name="dstSampleRate">目标采样率</param>
        /// <param name="filter">滤波器</param>
        /// <returns>重采样后的数据</returns>
        public static T[] Resample<T>(T[] data, int startIndex, int length, int srcSampleRate, int dstSampleRate, IResampleFilter<T> filter) where T : struct, IComparable
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            if (startIndex < 0)
            {
                startIndex = 0;
            }

            if (length < 0)
            {
                length = data.Length - startIndex;
            }
            else if (length > 0)
            {
                if (startIndex + length > data.Length)
                {
                    throw new ArgumentOutOfRangeException(nameof(length), $"目标数据长度\"{length}\"超出范围,最大只能为\"{data.Length - startIndex}\"");
                }
            }

            if (length == 0)
            {
                return new T[0];
            }

            if (srcSampleRate <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(srcSampleRate), "原始采样率不能小于等于0");
            }

            if (dstSampleRate <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(dstSampleRate), "目标采样率不能小于等于0");
            }

            if (dstSampleRate == srcSampleRate)
            {
                //采样率相同,不用重采了
                return data;
            }


            T[] results;
            int step, index, lcm;

            if (dstSampleRate > srcSampleRate)
            {
                //目标采样更大 => 小采大
                if (dstSampleRate % srcSampleRate == 0)
                {
                    //目标采样与原始采样率为整数倍关系,插值后再滤波
                    step = dstSampleRate / srcSampleRate;
                    results = new T[length * step];
                    index = startIndex;
                    for (int i = 0; i < results.Length; i += step)
                    {
                        results[i] = data[index];
                        index++;
                    }

                    filter.Filter(results);
                }
                else
                {
                    //目标采样与原始采样率不为整数倍关系,计算最小公倍数后,插值再滤波,再抽取
                    lcm = MathEx.LCM(srcSampleRate, dstSampleRate);//计算最小公倍数

                    //插值
                    step = lcm / srcSampleRate;
                    index = startIndex;

                    T[] buffer = new T[lcm * (data.Length / srcSampleRate)];
                    for (int i = 0; i < buffer.Length; i += step)
                    {
                        buffer[i] = data[index];
                        index++;
                    }

                    //滤波
                    filter.Filter(buffer);

                    //抽取
                    step = lcm / dstSampleRate;
                    results = new T[buffer.Length / step];
                    index = 0;
                    for (int i = 0; i < buffer.Length && index < results.Length; i += step)
                    {
                        results[index] = buffer[i];
                        index++;
                    }
                }
            }
            else
            {
                //目标采样更小 => 大采小
                if (srcSampleRate % dstSampleRate == 0)
                {
                    //目标采样与原始采样率为整数倍关系,直接抽取值
                    step = srcSampleRate / dstSampleRate;
                    results = new T[length / step];
                    index = 0;
                    int endIndex = startIndex + length;
                    for (int i = startIndex; i < endIndex && index < results.Length; i += step)
                    {
                        results[index] = data[i];
                        index++;
                    }
                }
                else
                {
                    //目标采样与原始采样率不为整数倍关系,计算最小公倍数后,插值再滤波,再抽取
                    lcm = MathEx.LCM(srcSampleRate, dstSampleRate);//计算最小公倍数

                    //插值
                    step = lcm / srcSampleRate;
                    index = startIndex;
                    T[] buffer = new T[length * step];
                    for (int i = 0; i < buffer.Length; i += step)
                    {
                        buffer[i] = data[index];
                        index++;
                    }

                    //滤波
                    filter.Filter(buffer);

                    //抽取
                    step = lcm / dstSampleRate;

                    int count = buffer.Length / step;
                    if (buffer.Length % step != 0)
                    {
                        //存在有余数的情况,当有余数的时候,余数点部分算作一个点
                        count++;
                    }
                    results = new T[count];
                    index = 0;
                    for (int i = 0; i < buffer.Length; i += step)
                    {
                        results[index] = buffer[i];
                        index++;
                    }
                }
            }

            return results;
        }

        #endregion






        


    }


    /// <summary>
    /// 重采样滤波器
    /// </summary>
    public interface IResampleFilter<T>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="values"></param>
        void Filter(T[] values);
    }
}
